﻿using Microsoft.AspNet.Authorization;
using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Mvc;
using Microsoft.Data.Entity;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.PlatformAbstractions;
using Sino.CapacityCloud.Infrastructure.Filters;
using Sino.CapacityCloud.Repositories.Models;
using Sino.CapacityCloud.Services;
using Sino.WebApi.Framework.Core.Filters;
using Sino.WebApi.Framework.Responses;
using System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;

namespace Sino.CapacityCloud.WebApi.ApiControllers
{

    public class BankCardController
        : ApiController<BankCardController>
    {

        private readonly Services.IBankCardService _bankCard;

        public BankCardController(
            IHostingEnvironment env,
            IApplicationEnvironment applicationEnvironment,
            IDistributedCache cache,
            ITokenGenertationService tokenGenertationService,
            ApplicationDbContext dbContext,
            ApplicationUserManager userManager,
            ApplicationRoleManager roleManager,
            ApplicationSignInManager signInManager,
            ILoggerFactory loggerFactory, Services.IBankCardService bankCard)
            : base(env, applicationEnvironment, cache, tokenGenertationService, dbContext, userManager, roleManager, signInManager, loggerFactory)
        {
            this._bankCard = bankCard;
        }

        public async Task<JsonResult> GetBankInfos()
        {
            var list = await this.DataContext.Banks.ToListAsync();
            return Json(list);
        }

        public async Task<JsonResult> GetBankName(string accountName, string bankCardNumber)
        {
            try
            {
                var verify = await this._bankCard.VerifyBankCardAsync(accountName, bankCardNumber);
                if (verify.Code == 0)
                {
                    return await this.PackageResultAsync(new ValueResponse<string>() { Value = verify.Data.BankName });
                }
                throw new ArgumentOutOfRangeException("The given bank card number is not valid.");
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to get bank name for the bank card because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<ValueResponse<string>>(ex);
            }
        }

        public async Task<JsonResult> AddBankCard(string accountName, string bankCardNumber)
        {
            try
            {
                var user = await this.UserManager.FindByNameAsync(this.User.Identity.Name);
                if (user != null)
                {
                    var bankCardCount = this.DataContext.BankCards.Where(x => x.UserId == user.Id).Count();

                    if (bankCardCount == 10)
                    {
                        throw new InvalidOperationException("Exceed maximum number of bank cards for one user.");
                    }

                    var verify = await this._bankCard.VerifyBankCardAsync(accountName, bankCardNumber);
                    if (verify.Code == 0)
                    {
                        var bankCard = this.DataContext.BankCards.FirstOrDefault(x => x.UserId == user.Id && x.Number == bankCardNumber);
                        if (bankCard != null)
                        {
                            throw new ArgumentException($"There is already a card with the save card number given under the user {user.Id}.");
                        }
                        var bank = this.DataContext.Banks.FirstOrDefault(x => x.Name == verify.Data.BankName);
                        if (bank == null)
                        {
                            bank = new Bank()
                            {
                                Name = verify.Data.BankName,
                                LogoKey = verify.Data.LogoUrl,
                                Code = verify.Data.BankNum,
                            };
                            this.DataContext.Banks.Add(bank);
                            this.DataContext.SaveChanges();
                        }
                        // TODO: Add CardType.
                        bankCard = new Sino.CapacityCloud.Repositories.Models.BankCard()
                        {
                            UserId = user.Id,
                            Holder = accountName,
                            Number = bankCardNumber,
                            Type = verify.Data.CardType,
                            BankId = bank.BankId
                        };
                        this.DataContext.BankCards.Add(bankCard);
                        this.DataContext.SaveChanges();
                        var response = new BankCardInfoResponse()
                        {
                            Card = new BankCard()
                            {
                                Id = bankCard.BankCardId,
                                Number = bankCard.Number,
                                Holder = bankCard.Holder,
                                BankName = bankCard.Bank.Name,
                                BankImage = SystemUtils.Try(() => new HttpClient().GetAsync(
                                    verify.Data.LogoUrl).GetAwaiter().GetResult().IsSuccessStatusCode ?
                                        verify.Data.LogoUrl : null)
                            }
                        };
                        return await this.PackageResultAsync<BankCardInfoResponse>(response);
                    }
                    throw new SystemException($"The given combination of account name and card number is not valid.");
                }
                throw new SystemException("Failed to find the specified ApplicationUser object.");
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to add bank card because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<BankCardInfoResponse>(ex);
            }
        }
        public async Task<JsonResult> AddUserBankCard(string userId, string accountName, string bankCardNumber)
        {
            try
            {
                var user = await this.UserManager.FindByIdAsync(userId);
                if (user != null)
                {
                    var verify = await this._bankCard.VerifyBankCardAsync(accountName, bankCardNumber);
                    if (verify.Code == 0)
                    {
                        var bankCard = this.DataContext.BankCards.FirstOrDefault(x => x.UserId == user.Id && x.Number == bankCardNumber);
                        if (bankCard != null)
                        {
                            throw new ArgumentException($"There is already a card with the save card number given under the user {user.Id}.");
                        }
                        var bank = this.DataContext.Banks.FirstOrDefault(x => x.Name == verify.Data.BankName);
                        if (bank == null)
                        {
                            bank = new Bank()
                            {
                                Name = verify.Data.BankName,
                                LogoKey = verify.Data.LogoUrl,
                                Code = verify.Data.BankNum,
                            };
                            this.DataContext.Banks.Add(bank);
                            this.DataContext.SaveChanges();
                        }
                        // TODO: Add CardType.
                        bankCard = new Sino.CapacityCloud.Repositories.Models.BankCard()
                        {
                            UserId = user.Id,
                            Holder = accountName,
                            Number = bankCardNumber,
                            Type = verify.Data.CardType,
                            BankId = bank.BankId
                        };
                        this.DataContext.BankCards.Add(bankCard);
                        this.DataContext.SaveChanges();
                        var response = new BankCardInfoResponse()
                        {
                            Card = new BankCard()
                            {
                                Id = bankCard.BankCardId,
                                Number = bankCard.Number,
                                Holder = bankCard.Holder,
                                BankName = bankCard.Bank.Name,
                                BankImage = SystemUtils.Try(() => new HttpClient().GetAsync(
                                    verify.Data.LogoUrl).GetAwaiter().GetResult().IsSuccessStatusCode ?
                                        verify.Data.LogoUrl : null)
                            }
                        };
                        return await this.PackageResultAsync<BankCardInfoResponse>(response);
                    }
                    throw new SystemException($"The given combination of account name and card number is not valid.");
                }
                throw new SystemException("Failed to find the specified ApplicationUser object.");
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to add bank card because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<BankCardInfoResponse>(ex);
            }
        }
        public async Task<JsonResult> DeleteBankCard(string bankCardNumber)
        {
            try
            {
                var user = await this.UserManager.FindByNameAsync(this.User.Identity.Name);
                if (user != null)
                {
                    var card = this.DataContext.BankCards.FirstOrDefault(x => x.Number == bankCardNumber && x.UserId == user.Id);
                    if (card != null)
                    {
                        if (card.UserId == user.Id)
                        {
                            this.DataContext.BankCards.Remove(card);
                            this.DataContext.SaveChanges();
                            return await this.PackageResultAsync<VoidResponse>();
                        }
                        throw new SystemException($"The given bank card id {bankCardNumber} doesn't belong to the current user.");
                    }
                    throw new SystemException($"The given bank card id {bankCardNumber} is not found.");
                }
                throw new SystemException("Failed to find the specified ApplicationUser object.");
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to delete bank card because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<VoidResponse>(ex);
            }
        }
        public async Task<JsonResult> DeleteUserBankCard(string userId, string bankCardNumber)
        {
            try
            {
                var user = await this.UserManager.FindByIdAsync(userId);
                if (user != null)
                {
                    var card = this.DataContext.BankCards.FirstOrDefault(x => x.Number == bankCardNumber && x.UserId == user.Id);
                    if (card != null)
                    {
                        if (card.UserId == user.Id)
                        {
                            this.DataContext.BankCards.Remove(card);
                            this.DataContext.SaveChanges();
                            return await this.PackageResultAsync<VoidResponse>();
                        }
                        throw new SystemException($"The given bank card id {bankCardNumber} doesn't belong to the current user.");
                    }
                    throw new SystemException($"The given bank card id {bankCardNumber} is not found.");
                }
                throw new SystemException("Failed to find the specified ApplicationUser object.");
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to delete bank card because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<VoidResponse>(ex);
            }
        }
        public async Task<JsonResult> GetMyBankCards()
        {
            try
            {
                var user = await this.UserManager.FindByNameAsync(this.User.Identity.Name);
                if (user != null)
                {
                    var cards = this.DataContext.BankCards.Include(x => x.Bank).Where(x => x.UserId == user.Id).ToList();
                    var response = new BankCardListResponse()
                    {
                        BankCards = cards.Select(x => new BankCard()
                        {
                            Id = x.BankCardId,
                            Number = x.Number,
                            Holder = x.Holder,
                            BankName = x.Bank.Name,
                            BankImage = x.Bank.LogoKey
                        }).ToArray()
                    };
                    return await this.PackageResultAsync<BankCardListResponse>(response);
                }
                throw new SystemException("Failed to find the specified ApplicationUser object.");
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to get bank cards info because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<BankCardListResponse>(ex);
            }
        }
        public async Task<JsonResult> GetUserBankCards(string userId)
        {
            try
            {
                var user = await this.UserManager.FindByIdAsync(userId);
                if (user != null)
                {
                    var cards = this.DataContext.BankCards.Include(x => x.Bank).Where(x => x.UserId == user.Id).ToList();
                    var response = new BankCardListResponse()
                    {
                        BankCards = cards.Select(x => new BankCard()
                        {
                            Id = x.BankCardId,
                            Number = x.Number,
                            Holder = x.Holder,
                            BankName = x.Bank.Name,
                            BankImage = x.Bank.LogoKey
                        }).ToArray()
                    };
                    return await this.PackageResultAsync<BankCardListResponse>(response);
                }
                throw new SystemException("Failed to find the specified ApplicationUser object.");
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to get bank cards info because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<BankCardListResponse>(ex);
            }
        }

    }

}